ddPCR HIV RNA Processing

Author

Antoine Chaillon

Published

February 18, 2026

Equations

\[ \text{1. cDNA Synthesis Concentration: } C_{cDNA} = \frac{C_{RNA} \times V_{RNA\_in\_cDNA}}{V_{Total\_cDNA\_rxn}} \]

\[ \text{2. cDNA Mass per Well: } Mass_{well} = \left( C_{cDNA} \times \frac{V_{cDNA\_in\_PCR}}{V_{PCR\_rxn}} \right) \times V_{PCR\_rxn} \]

\[ \text{3. Final Normalization: } \frac{Copies}{1000ng} = \frac{Copies_{well}}{Mass_{well}} \times 1000 \]

Assay Parameters (to adjust)

Table 1: Assay setup parameters and volumes.

Description

Symbol

Value

Unit

RNA volume used in cDNA synthesis

VOL_RNA_IN_CDNA

15

µL

Total volume of cDNA synthesis reaction

TOTAL_VOL_CDNA

20

µL

cDNA volume used in ddPCR reaction

VOL_CDNA_IN_PCR

8

µL

Total volume of ddPCR reaction

VOL_PCR_RXN

20

µL

Load Map Files

Show the R code
# --- Define Paths ---
map_dir <- "/Users/antoinechaillon/Dropbox/_QA/Lab Files/Map Files/2022-09-01/"
map_files <- list.files(map_dir, pattern = "*.xlsx", full.names = TRUE)

# --- Cleaned Map Processing ---
map_df <- map_files %>%
        # Read all files and combine automatically
        map_df(~read_xlsx(.x, sheet = "Sheet1")) %>%
        # 1. Handle MBN and TP filters
        # Drop samples with no MBN (likely blanks or non-samples)
        filter(!is.na(`MBN DNA extraction`)) %>%
        # 2. Recode and Calculate
        mutate(
                `ddPCR ID#`       = as.character(`ddPCR ID`),
                sampling_date     = as.Date(`Sample Date`),
                coextraction_date = as.Date(`DNA extraction date`),
                run_date          = as.Date(Date),
                
                # Use the volumes/concs for math later
                # If this is RNA data, ensure the column name matches your Excel (e.g., 'RNA Concentration')
                RNA_vol_ul        = `volume uL/aliquot`,
                RNA_conc_ng_ul    = `DNA Concentration ng/uL`, # Assuming this is your RNA source
                
                # DNA template calculation from your old code
                `DNA template used` = (1000 / 7.1) * RNA_vol_ul / RNA_conc_ng_ul
        ) %>%
        # 3. Select and Standardize Column Names
        select(
                `ddPCR ID#`, 
                MBN               = `MBN DNA extraction`, 
                GlobalSpecimenID  = `Global Specimen ID`, 
                pid               = OPID, 
                location          = Primary, 
                comments          = Comments,
                sampling_date, 
                coextraction_date, 
                RNA_vol_ul, 
                RNA_conc_ng_ul,
                PI, 
                Project, 
                run               = `ddPCR Run #`, 
                run_date
        )

# Preview the cleaned map
# head(map_df)

datatable(
  map_df,
  caption = 'Mapping Samples',
  filter = 'top',
  options = list(pageLength = 10, autoWidth = TRUE, scrollX = TRUE),
  rownames = FALSE
) 

Process ddPCR Raw Data

Show the R code
ddpcr_path <- "../Lab Files/Raw Files/ddPCR RNA/"
ddpcr_files <- list.files(ddpcr_path, pattern = "cDNA.xlsx", full.names = TRUE)



# ---  Processing Function ---
process_ddpcr <- function(file_path) {
  # Extract Run ID from filename
  run_id <- gsub("_cDNA.xlsx", "", basename(file_path))
  
  read_xlsx(file_path) %>%
    # Use the column names confirmed by your dput 
    select(Sample, Target, Concentration, Status) %>%
    mutate(
      # FIX: Force Concentration to numeric to prevent the 'bind_rows' error
      Concentration = as.numeric(Concentration), 
      Sample = as.character(Sample)
    ) %>%
    # Filter out non-numeric samples and keep only valid statuses 
    filter(!is.na(as.numeric(Sample)), is.na(Status)) %>% 
    # Pivot so targets become columns
    pivot_wider(id_cols = Sample, names_from = Target, values_from = Concentration) %>%
    mutate(RunID = run_id)
}

ddpcr_data <- ddpcr_files %>%
  map_df(~process_ddpcr(.x), .id = "runnum") %>%
  mutate(`ddPCR ID#` = paste0(runnum, "-", Sample))
datatable(
  ddpcr_data|>dplyr::select(runnum,Sample, `ddPCR ID#`,
                            `ms tat Rev FAM`, `Gag HEX`),
  caption = 'Preprocessing Results',
  filter = 'top',
  options = list(pageLength = 10, autoWidth = TRUE, scrollX = TRUE),
  rownames = FALSE
) %>%
  formatRound(columns = 4:5, digits = 2) %>%
  formatStyle(
    c('ms tat Rev FAM', 'Gag HEX'),
    backgroundColor = '#f0f8ff',
    fontWeight = 'bold'
  )

Join and Calculate

Show the R code
# Using the ddpcr_data tibble you just generated
final_results <- map_df %>%
  inner_join(ddpcr_data, by = "ddPCR ID#") %>%
  mutate(
    # cDNA Synthesis Calculation
    # cDNA concentration = (RNA concentration * RNA volume) / total synthesis volume
    cDNA_conc_ul = (RNA_conc_ng_ul * VOL_RNA_IN_CDNA) / TOTAL_VOL_CDNA,
    
    # Concentration in the ddPCR reaction
    cDNA_conc_ddpcr = (cDNA_conc_ul * VOL_CDNA_IN_PCR) / VOL_PCR_RXN,
    
    # Mass of cDNA per well
    ng_well_cDNA = cDNA_conc_ddpcr * VOL_PCR_RXN,
    
    # Absolute copies per well
    msTatRev_well = `ms tat Rev FAM` * VOL_PCR_RXN,
    skGag_well    = `Gag HEX` * VOL_PCR_RXN,
    
    # Final Normalization to 1000ng
    msTatRev_1000ng = (msTatRev_well / ng_well_cDNA) * 1000,
    skGag_1000ng    = (skGag_well / ng_well_cDNA) * 1000
  )

Final Results

Show the R code
dt_display <- final_results %>%
  select(
    RunID,Sample, pid, location,
    `RNA Input (ng/uL)` = RNA_conc_ng_ul,
    `cDNA Stock (ng/uL)` = cDNA_conc_ul,
    `cDNA in Well (ng)` = ng_well_cDNA,
    `msTatRev (cp/uL)` = `ms tat Rev FAM`,
    `skGag (cp/uL)` = `Gag HEX`,
    `msTatRev / 1000ng` = msTatRev_1000ng,
    `skGag / 1000ng` = skGag_1000ng
  )




datatable(
  dt_display,
  caption = 'Final Processed Results (Use buttons below to download)',
  filter = 'top',
  extensions = 'Buttons',
  options = list(
    dom = 'Bfrtip',      
    buttons = list(
      list(extend = 'copy', title = NULL),
      list(extend = 'csv',  title = paste0("ddPCR_Results_", Sys.Date())),
      list(extend = 'excel', title = paste0("ddPCR_Results_", Sys.Date())),
      list(extend = 'pdf',   title = paste0("ddPCR_Results_", Sys.Date()))
    ),
    pageLength = 10, 
    autoWidth = TRUE, 
    scrollX = TRUE
  ),
  rownames = FALSE
) %>%
  formatRound(columns = 5:11, digits = 2) %>%
  formatStyle(
    c('msTatRev / 1000ng', 'skGag / 1000ng'),
    backgroundColor = '#f0f8ff',
    fontWeight = 'bold'
  )